/**
 * XHTMLを扱うクラス
 * Specification:
 *   XHTML™ 1.1 - Module-based XHTML - Second Edition
 *   http://www.w3.org/TR/xhtml11/
 *   RDFa in XHTML: Syntax and Processing
 *   http://www.w3.org/TR/rdfa-syntax/
 *   Dublin Core in HTML/XHTML meta and link elements
 *   http://dublincore.org/documents/dcq-html/
 */

module nazo.xhtml;
import nazo.string, std.stdio, std.contracts, nazo.http, core.memory;

enum XhtmlVersion{
  XHTML_1_0_TRADITIONAL,
  XHTML_1_0_STRICT,
  XHTML_1_0_FRAMESET,
  XHTML_1_1,
  XHTML_BASIC_1_0,
  XHTML_BASIC_1_1,
  XHTML_PRINT,
  XHTML_MOBILE_PROFILE_1_2, // application/vnd.wap.xhtml+xml
  XHTML_1_1_PLUS_MATHML_2_0,
  XHTML_1_1_PLUS_MATHML_2_0_PLUS_SVG_1_1,
  XHTML_PLUS_RDFA,
}

/**
 * xhtmlなクラス
 */
class Xhtml(XhtmlVersion ver = XhtmlVersion.XHTML_1_1){
  static assert(ver == XhtmlVersion.XHTML_1_1 ||
                  ver == XhtmlVersion.XHTML_PLUS_RDFA, "not impl");
  public{
    ///メニュー
    Menu* nl;
    ///言語
    string lang;
    ///リンク
    Link*[] links;
    ///メタ情報
    Meta[] metas;
    ///スタイル情報
    Style[] styles;
    ///スクリプト
    string[] scripts;
    ///タイトル
    string title;
    ///一番上のセクション
    Section ownerSec;
    ///ヘッダ
    string header;
    ///フッタ
    string footer;
    ///処理命令
    string pi;
    ///Javascriptでクッキーを使うかどうか
    bool useJSCookie=false;
    protected this(){}
    this(string title){
      this.title=title;
    }
    this(Section sec,string title){
      this.ownerSec=sec;
      this.title=title;
    }
    this(Section sec,Menu* nl,string title){
      this.ownerSec=sec;
      this.nl=nl;
      this.title=title;
    }
    ///コンテント
    string getContent(){
      string re = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
      re ~= pi;
      static if(ver == XhtmlVersion.XHTML_1_1){
        //re ~= `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`;
        //re ~= `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.satoshii.org/dtd/xhtml/xhtml11-msxml-m12n.dtd">`;
        re ~= `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "`~Http.basePath~`xhtml11-flat.dtd">`/*hack for MSXML*/ "\n";
        re ~= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"" ~ this.lang ~ "\">\n";
        re ~= "<head profile=\"http://dublincore.org/documents/dcq-html/\">\n";
        re ~= "<title>" ~ Escape.text(this.title) ~ "</title>\n";
        re ~= "<link rel=\"schema.DC\" href=\"http://purl.org/dc/elements/1.1/\"/>\n";
        re ~= "<link rel=\"schema.DCTERMS\" href=\"http://purl.org/dc/terms/\"/>\n";
      }else if(ver == XhtmlVersion.XHTML_PLUS_RDFA){
        re ~= `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">`\n; // FIXME: maybe msxml hack is needed
        re ~= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"" ~ this.lang ~ "\" version=\"XHTML+RDFa 1.0\"";
        re ~= " xmlns:foaf=\"http://xmlns.com/foaf/0.1/\"";
        re ~= " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"";
        re ~= " xmlns:dcterms=\"http://purl.org/dc/terms/\"";
        re ~= " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
        re ~= " typeof=\"foaf:Document\">\n";
        re ~= "<head>\n";
        re ~= "<title property=\"dc:title\">" ~ Escape.text(this.title) ~ "</title>\n";
      }
      re ~= "<script type=\"application/javascript\">\n";
      re ~= "if(window.parent!=null&amp;&amp;window.parent!=window)window.parent.location.replace(window.location.href);\n";
      re ~= "if(window.opener!=null&amp;&amp;window.opener!=window)window.opener.location.replace(window.location.href);\n";
      re ~= "if(window.top   !=null&amp;&amp;window.top   !=window)window.top   .location.replace(window.location.href);\n";
      re ~= "Window.prototype.close = null;\n";
      re ~= "Window.prototype.open = null;\n";
      re ~= "Window.prototype.openDialog = null;\n";
      re ~= "delete Window.prototype.status;\n";
      //FIXME: XMLHttpRequest、ActiveXObjectなどの削除
      if(!useJSCookie){
        re ~= "delete HTMLDocument.prototype.cookie;\n";
        //残念ながら消せない…
//        re~= "delete Window.prototype.location;\n"; // javascript:alert(document.defaultView.location)
        //抜け道はあるはずなので保険的。
      }
      re ~= "</script>\n";
      foreach(inout Link* l;links){
        re~=l.getContent(ver)~"\n";
      }
      if(metas.length>0)
        for(Meta* m=&metas[0];m<=&metas[$-1];m++){
          re~=m.getContent(ver);
        }
      if(styles.length>0)
        for(Style* s=&styles[0];s<=&styles[$-1];s++){
          re~="<link rel=\"stylesheet\""~(s.title!=""?" title=\""~s.title~"\"":"")~" href=\""~s.href~"\" type=\"text/css\"/>\n";
        }
      foreach(inout string i;this.scripts){
        re~="<script src=\""~i~"\" type=\"application/javascript\"/>\n";
      }
      re=nazo.string.join(re,"</head>\n<body>\n",this.header,(nl?menuToString(nl):""),this.ownerSec.getContents(),"\n<div id=\"footer\">\n",this.footer,"</div>\n</body>\n</html>");
      return re;
    }
  }
}

struct Style{
  string title;
  string href;
}

enum MetaType{
  NONE,
  DATETIME,
}

Meta createRDFMeta(string prop, string content, string name = "", MetaType mt = MetaType.NONE, string about = ""){
  Meta m;
  m.property = prop;
  m.content = content;
  if(name.length) m.name = name;
  if(mt == MetaType.DATETIME){
    m.scheme = "DCTERMS.W3CDTF";
    m.datatype = "xsd:dateTime";
  }
  if(about.length) m.about = about;
  return m;
}

Meta createDCMeta(string name, string content, MetaType mt = MetaType.NONE, string about = ""){
  return createRDFMeta("dc:" ~ name, content, "DC." ~ name, mt, about);
}

Meta createDCTERMSMeta(string parent, string name, string content, MetaType mt = MetaType.NONE, string about = ""){
  return createRDFMeta("dcterms:" ~ name, content, "DC." ~ parent ~ name, mt, about);
}


struct Meta{
  string name;
  string content;
  string property;
  string scheme;
  string datatype;
  string about;
  pure string getContent(XhtmlVersion ver){
    string r="<meta";
    if(ver == XhtmlVersion.XHTML_PLUS_RDFA){
      if(about.length)
        r ~= " about=\"" ~ about ~ "\"";
      if(property.length)
        r ~= " property=\"" ~ property ~ "\"";
      if(datatype.length)
        r ~= " datatype=\"" ~ datatype ~ "\"";
    }
    if(name.length)
      r ~= " name=\""~name~"\"";
    if(scheme.length)
      r ~= " scheme=\""~scheme~"\"";
    r ~= " content=\""~content~"\"/>\n";
    return r;
  }
}

///XHTML1.1のセクションを作成するクラス
///Bugs: hierarchyを足す場所が間違ってる(特に問題は無い)。
struct Section{
  private{
    //階層
    int hierarchy;
  }
  public{
    ///親
    Section* parent;
    ///タイトル
    string title;
    ///内容
    string contents;
    ///id
    string id;
    ///子供
    Section*[] children;
    void addChild(string title,string contents,string id="",Section*[] children = null){
      Section* sec = new Section;
      this.children~=sec.initialize(title,contents,hierarchy,&this,id,children);
    }
    ///セクションをXHTML1.1で返す
    string getContents(){
      string h=std.string.toString(this.hierarchy);
      string rv="<div class=\"section\"";
      if(id!="")rv=join(rv," id=\"",this.id,"\"");
      rv~=">\n<h"~h[0]~">"~this.title~"</h"~h[0]~">\n";
      if(this.contents.length!=0)rv=join(rv,"<div class=\"about\">",this.contents,"</div>\n");
      foreach(Section* i;this.children)
        rv=join(rv,i.getContents(),"\n");
      rv~="</div>";
      return rv;
    }
    ///XHTML1.1のセクションを作成する為のコンストラクタです。
    typeof(this)* initialize(string title,string contents,int parentHierarchy=0,Section* parent=null,string id="",Section*[] children = null){
      this.title=title;
      this.contents=contents;
      this.hierarchy=parentHierarchy+1;
      this.parent=parent;
      this.id=id;
      this.children = children;
      return &this;
    }
  }
}

///XHTML1.1のlinkのクラス
struct Link{
  public{
    ///関係の方向
    ///Bugs: enumで保持するべき?
    string direction; // rel or rev
    ///関係
    string relation;
    ///リソースの名前
    string title;
    ///リソースの場所
    string href;
    ///リソースのコンテントタイプ
    string type;
    ///about
    string about;
    ///リソース
    string resource;
    ///XHTML1.1のリンク要素を取得
    pure string getContent(XhtmlVersion ver){
      string re="<link "~this.direction~"=\""~this.relation~"\"";
      if(href.length)re~=" href=\""~href~"\"";
      if(type.length)re~=" type=\""~type~"\"";
      if(title.length)re~=" title=\""~title~"\"";
      if(ver == XhtmlVersion.XHTML_PLUS_RDFA){
        if(about.length) re ~= " about=\""~about~"\"";
        if(resource.length) re ~= " resource=\""~resource~"\"";
      }
      re~="/>";
      return re;
    }
  }
}

Link[] l;
uint i=6;
Link* newLink(){
  if(i==6){
    l=new Link[6];
    i=0;
  }
  return &l[i++];
}

Link* newLink(string relation,string href,string direction="rel",string type="",string title="", string about="", string resource=""){
  Link* t=newLink;
  t.relation=relation;
  t.href=href;
  t.direction=direction;
  t.title=title;
  t.type=type;
  t.about=about;
  t.resource=resource;
  return t;
}

Link* newResourceLink(string relation, string resource){
  Link* t=newLink;
  t.direction="rel";
  t.relation=relation;
  t.resource=resource;
  return t;
}

struct Menu{
  Menu[] children;
  string content;
}

string menuToString(Menu* m){
  uint g=0;
  string[] arr=menuToStrings(m, g);
  char[] re=(cast(char*)GC.malloc(g*char.sizeof))[0..g];
  int j;
//writefln(\n);
  foreach(string a;arr){
//writefln(a);
    re[j..j+a.length]=a[0..$];
    j+=a.length;
  }
  return cast(string)re;
}

string[] menuToStrings(Menu* m, ref uint g){
  string[] result;
  result.length=m.children.length*3+2;
  result[0]="<ul class=\"nl\">\n";
  g+="<ul class=\"nl\">\n".length+"</ul>\n".length;
  int i=1;
  for(Menu* c=&m.children[0];c<=&m.children[$-1];c++){
    result[i++]="<li>";
    if(c.children){
      string[] r=menuToStrings(c, g);
      result.length=result.length+r.length;
      result[i..i+r.length]=r;
      i+=r.length;
    }else{
      result[i++]=c.content;
      g+=c.content.length;
    }
    result[i++]="</li>\n";
    g+="<li>".length+"</li>\n".length;
  }
  result[$-1]="</ul>\n";
  return result;
}

/*string menuToString(Menu* m){
  string result="<ul class=\"nl\">\n";
  for(Menu* c=&m.children[0];c<=&m.children[$-1];c++){
    if(c.children)result~="<li>"~menuToString(c)~"</li>\n";
    else result~="<li>"~c.content~"</li>\n";
  }
  return result~"</ul>\n";
}*/

static struct Escape{
  static string text(string str){
    uint l=str.length;

    for(auto s=str.ptr;s<str.ptr+str.length;s++){
      switch(*s){
        case '<':
        case '>':
          l+=3;
          continue;
        case '&':
          l+=4;
          continue;
        default:
      }
    }
    if(l==str.length)
      return str;
    char* rv=cast(char*)GC.malloc(l*char.sizeof);
    uint i,n;
    for(;i<str.length;i++,n++){
      switch(str[i]){
        case '<':
          invariant t=(cast(uint[])"&lt;")[0];
          *cast(uint*)(rv+n)=t;
          n+=3;
          break;
        case '>':
          invariant t=(cast(uint[])"&gt;")[0];
          *cast(uint*)(rv+n)=t;
          n+=3;
          break;
        case '&':
          invariant t=(cast(uint[])"&amp")[0];
          *cast(uint*)(rv+n)=t;
          rv[n+4]=';';
          n+=4;
          break;
        default:rv[n]=str[i];
      }
    }
    return cast(string)rv[0..l];
  }
  static string arg(string str){
    uint l=str.length;

    for(auto s=str.ptr;s<str.ptr+str.length;s++){
      switch(*s){
        case '<':
        case '>':
          l+=3;
          continue;
        case '&':
          l+=4;
          continue;
        case '"':
        case '\'':
          l+=5;
          continue;
        default:
      }
    }
    if(l==str.length)
      return str;

    char* rv=cast(char*)GC.malloc(l*char.sizeof);
    uint i,n;
    for(;i<str.length;i++,n++){
      switch(str[i]){
        case '<':
          invariant t=(cast(uint[])"&lt;")[0];
          *cast(uint*)(rv+n)=t;
          n+=3;
          break;
        case '>':
          invariant t=(cast(uint[])"&gt;")[0];
          *cast(uint*)(rv+n)=t;
          n+=3;
          break;
        case '&':
          invariant t=(cast(uint[])"&amp")[0];
          *cast(uint*)(rv+n)=t;
          rv[n+4]=';';
          n+=4;
          break;
        case '"':
          invariant t=(cast(uint[])"&quo")[0];
          *cast(uint*)(rv+n)=t;
          invariant t2=(cast(ushort[])"t;")[0];
          *cast(ushort*)(rv+n+4)=t2;
          n+=5;
          break;
        case '\'':
          invariant t=(cast(uint[])"&apo")[0];
          *cast(uint*)(rv+n)=t;
          invariant t2=(cast(ushort[])"s;")[0];
          *cast(ushort*)(rv+n+4)=t2;
          n+=5;
          break;
        default:rv[n]=str[i];
      }
    }
    return cast(string)rv[0..l];
  }
}
